
import chisel3._
import chisel3.util._
import chiseltest._
import org.scalatest.{Matchers, FlatSpec}
import scala.util.Random

import comlib._
import comlib.fifo._

class FifoSpec extends FlatSpec with ChiselScalatestTester with Matchers {

  val rnd = new Random

  it should "初始状态检查" in {
    test( new CFifoEx(1.U(16.W), 2) ){ dut =>
      
      // ==== 初始状态检查
      dut.io.enq.poke(false.B) 
      dut.io.enq_data.poke(2.U) 
      dut.io.deq.poke(false.B) 

      dut.io.deq_data.expect(0.U)
      dut.io.deq_valid.expect(false.B)
      dut.io.full.expect(false.B)
      dut.io.empty.expect(true.B)
      dut.io.length.expect(0.U)
      dut.io.data(0).expect(0.U)
      dut.io.data(1).expect(0.U)

    }
  }

  it should "enq & deq" in {
    test( new CFifoEx(1.U(16.W), 2, hasLen=true) ){ dut =>
      
      // ==== Enqueue
      dut.io.deq.poke(false.B) 
      dut.io.enq.poke(true.B) 
      dut.io.enq_data.poke(2.U) 
      dut.clock.step()

      dut.io.deq_data.expect(2.U)
      dut.io.deq_valid.expect(true.B)
      dut.io.full.expect(false.B)
      dut.io.empty.expect(false.B)
      dut.io.data(0).expect(2.U)
      dut.io.data(1).expect(0.U)
      dut.io.length.expect(1.U)

      // ==== Enqueue
      dut.io.enq.poke(true.B)
      dut.io.enq_data.poke(3.U)
      dut.clock.step()

      dut.io.deq_data.expect(2.U)
      dut.io.deq_valid.expect(true.B)
      dut.io.full.expect(true.B)
      dut.io.empty.expect(false.B)
      dut.io.length.expect(2.U)
      dut.io.data(0).expect(2.U)
      dut.io.data(1).expect(3.U)

      // ==== Enqueue & Dequeue
      dut.io.enq.poke( true.B )
      dut.io.enq_data.poke(4.U)
      dut.io.deq.poke( true.B )
      dut.clock.step()

      dut.io.deq_data.expect(3.U)
      dut.io.deq_valid.expect(true.B)
      dut.io.full.expect(true.B)
      dut.io.empty.expect(false.B)
      dut.io.length.expect(2.U)
      dut.io.data(0).expect(3.U)
      dut.io.data(1).expect(4.U)

      // ==== Dequeue
      dut.io.enq.poke(false.B)
      dut.io.deq.poke(true.B)
      dut.clock.step()

      dut.io.deq_data.expect(4.U)
      dut.io.deq_valid.expect(true.B)
      dut.io.full.expect(false.B)
      dut.io.empty.expect(false.B)
      dut.io.length.expect(1.U)
      dut.io.data(0).expect(4.U)
      dut.io.data(1).expect(4.U)

      // ==== Dequeue
      dut.io.enq.poke(false.B)
      dut.io.deq.poke(true.B)
      dut.clock.step()

      dut.io.deq_data.expect(4.U)
      dut.io.deq_valid.expect(false.B)
      dut.io.full.expect(false.B)
      dut.io.empty.expect(true.B)
      dut.io.length.expect(0.U)
      dut.io.data(0).expect(4.U)
      dut.io.data(1).expect(4.U)

    }
  }

  it should "support overflow interupt" in {
    test( new CFifoEx(1.U(16.W), 4, hasLen=true, hasIntf=true) ){ dut =>

      // 默认信号
      dut.io.enq_data.poke(0.U)

      // 先把FIFO压满
      for( _ <- 0 until dut.size ){
        dut.io.enq.poke(true.B)
        dut.clock.step()
      }

      // 检查是否无上溢
      dut.io.of.expect(false.B)

      dut.clock.step()

      // 继续压入一个
      dut.io.enq.poke(true.B)

      // 检查是否上溢
      dut.io.of.expect(true.B)

      // 持续多个clock
      for( _ <- 0 until 10 ) dut.clock.step()

      // 检查是否上溢
      dut.io.of.expect(true.B)
    }
  }

  it should "support underflow interupt" in {
    test( new CFifoEx(1.U(16.W), 4, hasLen=true, hasIntf=true) ){ dut =>

      // 检查是否无下溢
      dut.io.uf.expect(false.B)

      dut.clock.step()

      // 抽一个
      dut.io.deq.poke(true.B)

      dut.clock.step()

      // 检查是否下溢
      dut.io.uf.expect(true.B)

      // 持续多个clock
      for( _ <- 0 until 10 ) dut.clock.step()

      // 检查是否下溢
      dut.io.uf.expect(true.B)
    }
  }

}

class SFifoSpec extends FlatSpec with ChiselScalatestTester with Matchers {

  it should "support enqueue" in {
    test( new SFifo(0.U(16.W), size=8, nDeq=3, nShift=3, debug=true) ){ dut =>

      var dataSn = 1

      // 初始检测
      for( o <- 0 until dut.nDeq ){
        dut.io.deq_valid(o).expect(false.B)
        dut.io.deq_data(o).expect(0.U)
      }

      val result = Seq(
        Seq(1, 0, 0), Seq(1, 2, 0), Seq(1, 2, 3)
      )

      // 入队size-nDeq个数，输出无效
      for( i <- 0 until dut.size ){
        // 激励
        dut.io.enq.poke(true.B)
        dut.io.enq_data.poke( dataSn.U )
        dataSn = dataSn + 1

        dut.clock.step()

        // Monitor
        for( o <- 0 until dut.nDeq ){
          val resPos = if( i >= result.size ) result.size-1 else i
          dut.io.deq_data(o).expect((result(resPos)(o)).U, f"i=${i} o=${o}")
          dut.io.deq_valid(o).expect((result(resPos)(o)!=0).B, f"i=${i} o=${o}")
        }
      }

    }
  }

  it should "support 3 deqs at same time and random deqFF" in {

    val rnd = new Random
    val deqFF_1 = rnd.nextInt(5)
    val deqFF = if(deqFF_1==1) 0 else deqFF_1

    test( new SFifo(0.U(16.W), size=8, nDeq=3, nShift=3, deqFF=deqFF, debug=true) ){ dut =>
      var dataSn = 1
      // 先入队压满
      for( _ <- 0 until dut.size ){
        dut.io.enq.poke(true.B)
        dut.io.enq_data.poke(dataSn.U)
        dut.clock.step()
        dataSn = dataSn + 1
      }

      val result = Seq(
        Seq(1,2,3), Seq(4,5,6), Seq(7,8,9), Seq(10,11,0), Seq(0,0,0)
      )

      // 测试同时出队3个
      for( i <- 0 until result.size){
        for( o <- 0 until dut.nDeq ){
          dut.io.deq_data(o).expect((result(i)(o)).U, f"i=${i} o=${o}")
          dut.io.deq_valid(o).expect((result(i)(o)!=0).B, f"i=${i} o=${o}")
          
          dut.io.deqs(o).poke(true.B)
        }
        dut.io.enq_data.poke(dataSn.U)  // 同时继续入队
        dut.clock.step()
        dataSn = dataSn + 1

      }

      dut.clock.step()
    }
  }

  it should "support two deqs: deq1&deq2 " in {
    test( new SFifo(0.U(16.W), size=8, nDeq=3, nShift=3, debug=true) ){ dut =>

      var dataSn = 1
      // 先入队压满
      for( _ <- 0 until dut.size ){
        dut.io.enq.poke(true.B)
        dut.io.enq_data.poke(dataSn.U)
        dut.clock.step()
        dataSn = dataSn + 1
      }

      val result = Seq(
        Seq(1,2,3), Seq(1,4,5), Seq(1,6,7), Seq(1,8,9), Seq(1,10,11), Seq(1,12,13), Seq(1,14,0)
      )

      // 测试同时出队3个
      for( i <- 0 until result.size){
        for( o <- 0 until dut.nDeq ){
          dut.io.deq_data(o).expect((result(i)(o)).U, f"i=${i} o=${o}")
          dut.io.deq_valid(o).expect((result(i)(o)!=0).B, f"i=${i} o=${o}")
          
          dut.io.deqs(o).poke((o>0).B)
        }
        dut.io.enq_data.poke(dataSn.U)  // 同时继续入队
        dut.clock.step()
        dataSn = dataSn + 1

      }

      dut.clock.step()

    } 
  }

  it should "support one deqs: deq0 " in {
    test( new SFifo(0.U(16.W), size=8, nDeq=3, nShift=3, debug=true) ){ dut =>

      var dataSn = 1
      // 先入队压满
      for( _ <- 0 until dut.size ){
        dut.io.enq.poke(true.B)
        dut.io.enq_data.poke(dataSn.U)
        dut.clock.step()
        dataSn = dataSn + 1
      }

      val result = Seq(
        Seq(1,2,3), Seq(2,3,4), Seq(3,4,5), Seq(4,5,6), Seq(5,6,7), Seq(6,7,8), Seq(7,8,9), Seq(8,9,10), Seq(9,10,11), Seq(10,11,12)
      )

      // 测试同时出队3个
      for( i <- 0 until result.size){
        for( o <- 0 until dut.nDeq ){
          dut.io.deq_data(o).expect((result(i)(o)).U, f"i=${i} o=${o}")
          dut.io.deq_valid(o).expect((result(i)(o)!=0).B, f"i=${i} o=${o}")
          
          dut.io.deqs(o).poke((o==0).B)
        }
        dut.io.enq_data.poke(dataSn.U)  // 同时继续入队
        dut.clock.step()
        dataSn = dataSn + 1

      }

      dut.clock.step()

    } 
  }

  it should "support one deqs: deq1 " in {
    test( new SFifo(0.U(16.W), size=8, nDeq=3, nShift=3, debug=true) ){ dut =>

      var dataSn = 1
      // 先入队压满
      for( _ <- 0 until dut.size ){
        dut.io.enq.poke(true.B)
        dut.io.enq_data.poke(dataSn.U)
        dut.clock.step()
        dataSn = dataSn + 1
      }

      val result = Seq(
        Seq(1,2,3), Seq(1,3,4), Seq(1,4,5), Seq(1,5,6), Seq(1,6,7), Seq(1,7,8), Seq(1,8,9), Seq(1,9,10), Seq(1,10,11)
      )

      // 测试同时出队3个
      for( i <- 0 until result.size){
        for( o <- 0 until dut.nDeq ){
          dut.io.deq_data(o).expect((result(i)(o)).U, f"i=${i} o=${o}")
          dut.io.deq_valid(o).expect((result(i)(o)!=0).B, f"i=${i} o=${o}")
          
          dut.io.deqs(o).poke((o==1).B)
        }
        dut.io.enq_data.poke(dataSn.U)  // 同时继续入队
        dut.clock.step()
        dataSn = dataSn + 1

      }

      dut.clock.step()

    } 
  }

  it should "support one deqs: deq2 " in {
    test( new SFifo(0.U(16.W), size=8, nDeq=3, nShift=3, debug=true) ){ dut =>

      var dataSn = 1
      // 先入队压满
      for( _ <- 0 until dut.size ){
        dut.io.enq.poke(true.B)
        dut.io.enq_data.poke(dataSn.U)
        dut.clock.step()
        dataSn = dataSn + 1
      }

      val result = Seq(
        Seq(1,2,3), Seq(1,2,4), Seq(1,2,5), Seq(1,2,6), Seq(1,2,7), Seq(1,2,8), Seq(1,2,9), Seq(1,2,10), Seq(1,2,11)
      )

      // 测试同时出队3个
      for( i <- 0 until result.size){
        for( o <- 0 until dut.nDeq ){
          dut.io.deq_data(o).expect((result(i)(o)).U, f"i=${i} o=${o}")
          dut.io.deq_valid(o).expect((result(i)(o)!=0).B, f"i=${i} o=${o}")
          
          dut.io.deqs(o).poke((o==2).B)
        }
        dut.io.enq_data.poke(dataSn.U)  // 同时继续入队
        dut.clock.step()
        dataSn = dataSn + 1

      }

      dut.clock.step()

    } 
  }

}